package autotd.server;

import java.awt.Point;
import java.io.*;
import java.net.*;
import java.util.*;

import autotd.*;

public class TDServer extends Thread implements NetworkProtocol, AdvancedSimulatorListener {

	private class ServerSocketListener extends Thread {
		public void run() {
			while (!myServerSocket.isClosed()) {
				try {
					Socket client = myServerSocket.accept();
					BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream()));
					PrintStream output = new PrintStream(client.getOutputStream());
					//send connect command
					output.println(CONNECTED_RESPONSE);
					output.flush();
					String line = input.readLine();
					
					while (line != null) {
						if (line.equals(WATCHER_REQUEST)) {
							output.println(ACK);
							output.flush();
							myWatchers.add(output);
							System.out.println("Watcher connected " + client.getInetAddress());
							break;
						} else if (line.equals(COMMANDER_REQUEST)) {
							output.println(ACK);
							output.flush();
							CommanderListener cl = new CommanderListener(input, output);
							cl.start();
							System.out.println("Commander connected " + client.getInetAddress());
							break;
						} else if (line.length() > 0) {
							System.err.println("Client sent an illegal request: " + line);
						}
						line = input.readLine();
					}
				} catch(Exception e) {
				}
			}
		}
	}

	private class CommanderListener extends Thread {
		private BufferedReader myInput;
		private PrintStream myOutput;

		public CommanderListener(BufferedReader input, PrintStream output) {
			myInput = input;
			myOutput = output;
		}

		public void run() {
			try {
				String nextLine = myInput.readLine();
				while (nextLine != null) {
					if (nextLine.length() > 0) {
						CommandRequest cr = new CommandRequest(myOutput, nextLine);
						myCommandQueue.add(cr);
					}
					nextLine = myInput.readLine();
				}
			} catch(Exception exp) {
				System.err.println("Commander client caused the exception " + exp);
			}
		}
	}

	private static class CommandRequest {
		public final PrintStream responseStream;
		public final String command;
		public CommandRequest(PrintStream response, String cmd) {
			responseStream = response;
			command = cmd;
		}
	}

	private ServerSocket myServerSocket;
	private List<PrintStream> myWatchers;
	private List<CommandRequest> myCommandQueue;

	private TDSimulator mySimulator;
	private FileWriter log;

	public TDServer(TDSimulator sim) throws IOException {
		mySimulator = sim;
		myWatchers = new Vector<PrintStream>();
		myCommandQueue = new Vector<CommandRequest>();
		myServerSocket = new ServerSocket(DEFAULT_PORT_NUMBER);
		(new ServerSocketListener()).start();
		mySimulator.addAdvancedListener(this);

		try {
			new File("logs").mkdir();
			log = new FileWriter("logs/" + System.currentTimeMillis() + ".txt");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void run() {
		System.out.println("TDServer taking requests...");
		while (!myServerSocket.isClosed()) {
			while (myCommandQueue.isEmpty()) {
				try {
					Thread.sleep(100);
				} catch(Exception e) {
				}
			}
			CommandRequest cr = myCommandQueue.remove(0);
			commandHandler(cr);
		}
		System.out.println("TDServer shutting down.");
	}
	
	private void commandHandler(CommandRequest cr) {
		String[] args = cr.command.split(CMD_DELIM);
		
		if(args[0].equals(CMD_PLACE_TOWER)) {
			boolean success = mySimulator.placeTower(mySimulator.getTowerType(args[3]), Integer.valueOf(args[1]), Integer.valueOf(args[2]));
			if (success) {
				log("PLACE_TOWER " + args[1] + " " + args[2] + " " + args[3]);
			}
			cr.responseStream.println(success ? ACK : FAIL);
			cr.responseStream.flush();
		} else if (args[0].equals(CMD_FIRE_TOWER)) {
			Point p = new Point(Integer.parseInt(args[1]), Integer.parseInt(args[2]));
			boolean success = mySimulator.shootMonster(p, mySimulator.lookupMonster(Integer.parseInt(args[3])));
			if (success) {
				log("FIRE_TOWER " + args[1] + " " + args[2] + " " + args[3]);
			}
			cr.responseStream.println(success ? ACK : FAIL);
			cr.responseStream.flush();
		} else if(args[0].equals(CMD_QUERY_PLAYER)) {
			String send = CMD_QUERY_PLAYER + CMD_DELIM + mySimulator.getPlayer().getMoney() + CMD_DELIM + mySimulator.getPlayer().getLives();
			cr.responseStream.println(send);
			cr.responseStream.flush();	
		} else if(args[0].equals(CMD_QUERY_MONSTERS)) {
			String monsters = "", specials = "";
			for(Monster m: mySimulator.getActiveMonsters()) {
				for(String s: m.specials)
					specials += s;
				monsters += m.id + CMD_DELIM + m.pos.x + CMD_DELIM + m.pos.y + CMD_DELIM + m.health + CMD_DELIM + m.delay + CMD_DELIM + m.Type.Height + CMD_DELIM + specials + CMD_LIST_DELIM;
			}
			
			if(!mySimulator.getActiveMonsters().isEmpty())
				monsters = monsters.substring(0, monsters.lastIndexOf(CMD_LIST_DELIM));
			
			String send = CMD_QUERY_MONSTERS + CMD_DELIM + monsters + CMD_DELIM;
			cr.responseStream.println(send);
			cr.responseStream.flush();
		} else if(args[0].equals(CMD_TICK)) {
			int ticks = 1;
			if(args.length != 1) {
				ticks = Integer.parseInt(args[1]);
			}
			for(int i = 0; i < ticks; ++i) {
				log("TICK " + mySimulator.turn);
				mySimulator.tick();
			}
			cr.responseStream.println(ACK);
			cr.responseStream.flush();
		} else if(args[0].equals(CMD_QUERY_TOWERS)) {
			String queryTowers = "";
			for(Tower t: mySimulator.getTowers()) {
				queryTowers += t.pos.x + CMD_DELIM + t.pos.y + CMD_DELIM + t.height + CMD_DELIM + t.Type.Name + CMD_LIST_DELIM; 
			}
			if(!mySimulator.getTowers().isEmpty())
				queryTowers = queryTowers.substring(0, queryTowers.lastIndexOf(CMD_LIST_DELIM));
			
			String send = CMD_QUERY_TOWERS + CMD_DELIM + queryTowers;
			cr.responseStream.println(send);
			cr.responseStream.flush();
		} else {
			System.err.println("Client sent unknown command: " + cr.command);
			cr.responseStream.println(FAIL);
			cr.responseStream.flush();
		}
	}



	public void log(String entry) {
		try {
			log.write(entry + "\r\n");
			log.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}




	private void sendStatusUpdate(String msg) {
		PrintStream[] destinations = myWatchers.toArray(new PrintStream[0]);
		for (PrintStream dest : destinations) {
			dest.println(msg);
			dest.flush();
		}
	}
	public void monsterCreated(int mID, String type, int level, int x, int y) {
		sendStatusUpdate(MONSTER_UPDATE + " " + MONSTER_CREADTED_UPDATE + " " + mID + " " + type + " " + level + " " + x + " " + y);
	}
	public void monsterMoved(int mID, int prevX, int prevY, int currX, int currY) {
		sendStatusUpdate(MONSTER_UPDATE + " " + MONSTER_MOVED_UPDATE + " " + mID + " " + prevX + " " + prevY + " " + currX + " " + currY);
	}
	public void monsterInjured(int mID, int health) {
		sendStatusUpdate(MONSTER_UPDATE + " " + MONSTER_INJURED_UPDATE + " " + mID + " " + health);
	}
	public void monsterCompletedPath(int mID) {
		sendStatusUpdate(MONSTER_UPDATE + " " + MONSTER_COMPLETED_PATH_UPDATE + " " + mID);		
	}
	public void towerPlaced(int tID, String type, int x, int y, int height) {
		sendStatusUpdate(TOWER_UPDATE + " " + TOWER_PLACED_UPDATE + " " + tID + " " + type + " " + x + " " + y + " " + height);
	}
	public void towerFired(int tID, int mID) {
		sendStatusUpdate(TOWER_UPDATE + " " + TOWER_FIRED_UPDATE + " " + tID + " " + mID);
	}
	public void playerChange(int lives, int money) {
		sendStatusUpdate(PLAYER_UPDATE + " " + lives + " " + money);
	}
	public void turnChangedTo(int turnNum) {
		sendStatusUpdate(TURN_UPDATE + " " + turnNum);
	}




	public static void main(String[] args) throws Exception {
		String settingsFile = "";
		if (args.length == 0) {
			settingsFile = TDSimulator.DEFAULT_SETTINGS_FILE;
		} else {
			settingsFile = args[0];
		}
		TDSimulator sim = TDSimulator.pathSearchLoadSettings(settingsFile);
		if (sim == null) {
			System.err.println("Unable to load server settings. Please check to ensure that the file " + '"' + settingsFile + '"' + " actually exists and has the correct format!");
			System.exit(-1);
		}
		TDServer server = new TDServer(sim);
		server.start();
		System.out.println("TDServer now online");
	}
}
